#include <vector>
#include <algorithm>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include "SSLReceiver.h"
#include "ReceiverError.h"
#include "../data/Reply.h"
#include "../../comm/CommError.h"
#include "../../comm/ssl/SSLCommon.h"
#include "../../config/Config.h"
#include "../../mime/core/Base64Coder.h"
#include "../../system/Queues.h"
#include "../../system/Observers.h"
#include "../../utils/Time.h"

#define foreach BOOST_FOREACH

// ---------------------------------------------------------------- //

namespace kc_sf
{
	const std::string Wsp = "\t \r\n";
}

using namespace kc_sf;

// ---------------------------------------------------------------- //

SSLReceiver::SSLReceiver( const Connection &connection )
: Receiver( connection )
{
}

// ---------------------------------------------------------------- //

SSLReceiver::SSLReceiver(CommCommon *comm, const Connection &connection )
: Receiver( comm, connection )
{
}

// ---------------------------------------------------------------- //

SSLReceiver::~SSLReceiver()
{
	if (r_safe_session)
	{
		r_safe_session->close();
		delete r_safe_session;
	}

	if (r_comm)
	{
		delete r_comm;
		r_comm = NULL;
	}
}

// ---------------------------------------------------------------- //

CommRequestHandler *SSLReceiver::getInstance( const Connection &connection ) const
{
	return new SSLReceiver( connection );
}

// ---------------------------------------------------------------- //

void SSLReceiver::operator ()()
{
	if (r_comm == NULL)
	{
		r_safe_session = new SSLServerSession( r_connection.cSocketFd );
		SSL *ssl = r_safe_session->acceptClient();
		r_ssl_logger << detail << receiver << cidIn( r_connection.cCid ) << "Nawiązano bezpieczną sesję SSL" << flush;

		r_comm = new SSLCommon( r_connection.cSocketFd, ssl, MAX_LINE_LENGTH );
	}

	Receiver::operator ()();
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processEhlo(const Command &cmd)
{
	std::vector<std::string> esmtp;
	esmtp.push_back( "8BITMIME" );
	esmtp.push_back( "SIZE " + boost::lexical_cast<std::string>(
							Config::getInstance().cMaxMessageSize) );
	//esmtp.push_back( "PIPELINING" );
	//esmtp.push_back("CHUNKING");
	esmtp.push_back( "AUTH PLAIN LOGIN " ); //CRAM-MD5" );

	Reply reply = StandardReplies.at(Ok_Ready);
	foreach( std::string extension, esmtp )
	{
		reply.TextLines.push_back(extension);
	}

	return processBaseHelo(cmd, reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processAuthPlain()
{
	if (!isAllowedInState(AuthPlain))
		return sendReply((StandardReplies.at(No_BadCommandsSeq)));

	Reply reply = StandardReplies.at(More_Authentication);
	CommResult result = sendReply(reply);
	if (result != Success)
		return result;

	// \0login\0haslo
	// \0login\@domena.pl\0haslo

	std::string credentials;
	result = recvAndDecodeCredentials(credentials);
	if (result != Success)
	{
		reply = StandardReplies.at(No_AuthenticationSyntaxError);
		return sendReply(reply);
	}

	std::vector<std::string> words;
	words = boost::split(words, credentials, boost::is_any_of("\0\\@"));
	words.erase(std::remove(words.begin(), words.end(), " "), words.end());
	if (words.size() < 2)
		return sendReply((StandardReplies.at(No_SyntaxError)));

	std::string login = words[0], passwd = words[1];

	if (words.size() > 2)
	{
		login += "@" + words[1];
		passwd = words[2];
	}

	reply = checkAuthentication(login, passwd) ?
			StandardReplies.at(Ok_AuthenticationSuccess) :
			StandardReplies.at(No_AuthenticationFailure);

	return sendReply(reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processAuthLogin()
{
	if (!isAllowedInState(AuthLogin))
		return sendReply((StandardReplies.at(No_BadCommandsSeq)));

	Reply reply = StandardReplies.at(More_AuthenticationLogin);
	CommResult result = sendReply(reply);
	if (result != Success)
		return result;

	std::string login, passwd;

	// login
	result = recvAndDecodeCredentials(login);
	if (result != Success)
	{
		reply = StandardReplies.at(No_AuthenticationSyntaxError);
		return sendReply(reply);
	}

	reply = StandardReplies.at(More_AuthenticationPasswd);
	result = sendReply(reply);
	if (result != Success)
		return result;

	// haslo
	result = recvAndDecodeCredentials(passwd);
	if (result != Success)
	{
		reply = StandardReplies.at(No_AuthenticationSyntaxError);
		return sendReply(reply);
	}

	reply = checkAuthentication(login, passwd) ?
			StandardReplies.at(Ok_AuthenticationSuccess) :
			StandardReplies.at(No_AuthenticationFailure);

	return sendReply(reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processStartTLS()
{
	Reply reply = StandardReplies.at(Tmp_StartTLSRepeat);
	return sendReply(reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processMailFrom(const Command &cmd)
{
	if (!isAllowedInState(cmd.Type))
		return sendReply((StandardReplies.at(No_BadCommandsSeq)));

	// MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
	std::vector<std::string> words;
	words = boost::split(words, cmd.Data, boost::is_any_of(Wsp));

	if (words.empty())
		return sendReply((StandardReplies.at(No_SyntaxError)));

	// pierwsze slowo to <reverse-path>
	Address address;
	if (!stripSourceRoute(words[0], address))
		return sendReply(
				const_cast<const Reply &>(StandardReplies.at(No_SyntaxError)));

	// BODY=8BITMIME
	bool _8bitmime = false;
	foreach( std::string param, words )
	{
		if (boost::istarts_with(param, "body"))
		{
			_8bitmime = true;
			break;
		}
	}

	r_msg = RawMessage();
	r_msg.rmMailFrom = address;
	r_state = MailState;

	Reply reply = StandardReplies.at(Ok_Ok);
	reply.TextLines[0] = "<" + address.toString() + ">";
	reply.TextLines[0] +=
			_8bitmime ? " ... Sender and 8BITMIME ok" : " ... Sender ok";

	return sendReply(reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processRcpt(const Command &cmd)
{
	if (!isAllowedInState(cmd.Type))
		return sendReply(
				const_cast<const Reply &>(StandardReplies.at(No_BadCommandsSeq)));

	// parametry sa pomijane
	// nie zna lokalnych klientow - akceptuje wszystkich - open relay
	// wyluskaj z komendy adres nadawcy
	// RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
	// forward-path = route_adress,
	// route_adress:mailbox | Postmaster@Domain | Postmaster

	std::vector<std::string> words;
	words = boost::split(words, cmd.Data, boost::is_any_of(Wsp));

	if (words.empty())
		return sendReply((StandardReplies.at(No_SyntaxError)));

	// pierwsze slowo to <forward-path>
	Address address;
	if (!stripSourceRoute(words[0], address))
		return sendReply(
				const_cast<const Reply &>(StandardReplies.at(No_SyntaxError)));

	// uaktualnij nadchodzaca wiadomosc
	r_msg.rmRcptsTo.push_back(address);
	r_state = RcptState;

	Reply reply = StandardReplies.at(Ok_Ok);
	reply.TextLines[0] = "<" + address.toString() + "> ... Recipient ok";

	return sendReply(reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processData()
{
	if (!isAllowedInState(Data))
		return sendReply((StandardReplies.at(No_BadCommandsSeq)));

	CommResult result = sendReply(StandardReplies.at(More_StartMail));
	if (result != Success)
		return result;

	// [ mail data ] CRLF . CRLF
	std::string mail_data; // tresc wiadomosci

	if( ( result = recvData( mail_data, EOM ) ) != Success )
		return result;

//	std::string last_portion; // to, co odebrano w ostatniej iteracji
//	std::string previous_portion; // to, co odebrano w przedostatniej iteracji
//	while (!isEndOfMsg(previous_portion))
//	{
//		previous_portion = last_portion;
//		CommResult result = recvData(last_portion, "\r\n.\r\n");
//
//		std::cout << "IN WHILE" << std::endl;
//
//		if (result != Success)
//			return result;
//
//		std::cout << "STILL IN WHILE" << std::endl;
//
//		// aktualizacja tresci wiadomosci
//		mail_data += last_portion;
//		previous_portion += last_portion;
//
//		std::cout << "NEXT IT" << std::endl;
//	}

	// usuwanie kropki
	size_t dot = mail_data.find_last_of('.');
	if (dot != std::string::npos)
		mail_data.erase(dot, mail_data.size() - dot);

	r_msg.rmCameDate = now;
	r_msg.rmData = mail_data;
	r_msg.Id = MsgsObserver.size();
	r_logger << info << receiver << mid(r_msg.Id)
			<< "Odebrano nową wiadomość. Wielkość: " << mail_data.size() / 1000
			<< " " << mail_data.size() % 1000 << "B" << flush;

	r_logger << info << receiver << mid(r_msg.Id)
			<< "Nowa wiadomość w kolejce wiadomości przychodzących" << flush;

	InputMessageQueue.push(r_msg);
	InputMessageQueueObserver.add();
	MsgsObserver.add();

	r_state = DataState;

	Reply reply = StandardReplies.at(Ok_Ok);
	reply.TextLines = std::vector<std::string>(1, "Message OK");
	return sendReply(reply);
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::processBdat(const Command & cmd)
{
	// podziel odpowiedz na tokeny
	std::vector<std::string> words;
	words = boost::split(words, cmd.Data, boost::is_any_of(" "));

	if (words.empty())
		return sendReply((StandardReplies.at(No_SyntaxError)));

	// ile B?
	unsigned long bytes;
	try
	{
		bytes = boost::lexical_cast<unsigned long>(words[0]);
	} catch (boost::bad_lexical_cast &)
	{
		return sendReply((StandardReplies.at(No_SyntaxError)));
	}

	// odbierz dane
	std::string tmp;
	CommResult result = r_comm->recvData(tmp, bytes, DEF_RECEIVER_TIMEOUT_S);
	if (result != Success)
	{
		r_logger << error << receiver << mid(r_msg.Id)
				<< "Nie można odebrać wiadomości od klienta " << r_cl_name
				<< flush;
		return result;
	}

	r_state = BdatState;

	// czy to ostatnia porcja danych?
	bool last = words.size() > 1;
	if (last)
	{
		if (boost::iequals(words[1], "last"))
			return sendReply((StandardReplies.at(No_SyntaxError)));

		if (!isEndOfMsg(tmp))
			return sendReply((StandardReplies.at(No_SyntaxError)));

		// usuwanie kropki
		size_t dot = tmp.find_last_of('.');
		if (dot != std::string::npos)
			tmp.erase(dot, tmp.size() - dot);
	}

	// odpowiedz
	if (!isAllowedInState(Bdat))
		return sendReply((StandardReplies.at(No_BadCommandsSeq)));

	r_msg.rmData += tmp;
	if (last)
	{
		r_logger << info << receiver << mid(r_msg.Id)
				<< "Odebrano nową wiadomość. Wielkość: "
				<< r_msg.rmData.size() / 1000 << " "
				<< r_msg.rmData.size() % 1000 << "B" << flush;

		r_state = DataState;

		r_msg.rmCameDate = now;
		r_msg.Id = MsgsObserver.add() - 1;
		InputMessageQueue.push(r_msg);
		InputMessageQueueObserver.add();

		r_logger << info << receiver << mid(r_msg.Id)
				<< "Nowa wiadomość w kolejce wiadomości przychodzących"
				<< flush;
	}

	Reply reply = StandardReplies.at(Ok_Ok);
	std::stringstream reply_stream;
	reply_stream << "Message OK, " << bytes << " octets received";
	reply.TextLines = std::vector<std::string>(1, reply_stream.str());
	return sendReply(reply);
}

// ---------------------------------------------------------------- //

bool SSLReceiver::stripSourceRoute(const std::string &route_mailbox,
		Address &mailbox) const
{
	bool success = false;

	// reverse-path = route_adress,route_adress:mailbox | null
	if (!route_mailbox.empty() && route_mailbox[0] == '<'
			&& route_mailbox[route_mailbox.size() - 1] == '>')
	{
		std::vector<std::string> words;
		std::string route_mailbox_str = route_mailbox.substr(1,
				route_mailbox.size() - 2);
		words = boost::split(words, route_mailbox_str, boost::is_any_of(",:"));

		if (words.empty()) // null reverse-path
		{
			mailbox = Address();
			success = true;
		}
//        else if( boost::iequals( words[ words.size() -1 ], "postmaster" ) )   // postmaster
//        {
//            mailbox = Address( "postmaster", "" );
//            success = true;
//        }
		else if (!boost::iequals(words[words.size() - 1], "postmaster")) // poprawny adres
		{
			std::string address = words[words.size() - 1];
			size_t at = address.find('@');
			if (at != std::string::npos)
			{
				mailbox = Address(address.substr(0, at),
						address.substr(at + 1, address.size() - at - 1));
				success = true;
			}
		}
	}

	return success;
}

// ---------------------------------------------------------------- //

bool SSLReceiver::isEndOfMsg(const std::string &text) const
{
	std::cout << "TEXT: ";
	for( size_t i = 1; i < 11 && text.size() - i >= 0; ++i)
	{
		if( text[ text.size() - i ] == '\r' )
			std::cout << "<CR>";
		else if( text[ text.size() - i ] == '\n' )
			std::cout << "<LF>";
		else
			std::cout << text[ text.size() - i ];
	}
	std::cout << std::endl;

	bool success = false;
	bool crlf = false, dot = false;
	int j = 10;
	for (int i = text.size(); i > -1 && j > -1; --i, --j)
	{
		if (text[i] == '\r' || text[i] == '\n')
		{
			if (crlf && dot)
			{
				success = true;
				break;
			}
			crlf = true;
		}
		else if (text[i] == '.')
		{
			dot = true;
		}
		else
		{
			crlf = dot = false;
		}
	}

	return success;
}

// ---------------------------------------------------------------- //

bool SSLReceiver::checkAuthentication(const std::string &login,
		const std::string &passwd)
{
	// todo
	return true;
}

// ---------------------------------------------------------------- //

CommResult SSLReceiver::recvAndDecodeCredentials(std::string &decoded)
{
	std::string credentials;
	CommResult result = recvLine(credentials);
	if (result != Success)
		return result;

	// base64
	Base64Coder coder(MAX_LINE_LENGTH);
	return coder.decode(credentials, decoded) ? Success : Failure;
}
